# 标准库
import asyncio
import configparser
import json
import os
import random
import sys
import time
from datetime import datetime
from enum import Enum
from typing import Any, Dict, List, Optional
import subprocess
# 第三方库
import aiomysql
import execjs
import requests
from curl_cffi import requests as cf
import urllib3
from loguru import logger
from tqdm import tqdm
import uuid
import arrow
# 获取当天日期（可自定义格式）
now_str = arrow.now().format('YYYY-MM-DD')
# 关闭 urllib3 警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
_current_cookies = None
COOKIES_FILE = f'JDCS_CK_NODE_{now_str}.json'
class Config:
    # 请求配置
    MAX_RETRIES = 100
    RETRY_DELAY = 3
    REQUEST_INTERVAL = 3
# ====================================== 代理 ======================================
class ClashAPI:
    def __init__(self, port, secret):
        self.base_url = f'http://localhost:{port}'
        self.headers = {
            'Authorization': f'Bearer {secret}' if secret else ""
        }

    def get_all_proxies(self):
        """获取所有代理信息"""
        url = f'{self.base_url}/proxies'
        response = requests.get(url, headers=self.headers)
        if response.status_code == 200:
            return response.json()
        return None

    def get_proxy_info(self, name):
        """获取单个代理信息"""
        url = f'{self.base_url}/proxies/{name}'
        response = requests.get(url, headers=self.headers)
        if response.status_code == 200:
            return response.json()
        return None

    def get_proxy_delay(self, name, timeout=3000, test_url="http://www.gstatic.com/generate_204"):
        """获取代理延迟"""
        url = f'{self.base_url}/proxies/{name}/delay'
        params = {
            'timeout': timeout,
            'url': test_url
        }
        try:
            response = requests.get(url, params=params, headers=self.headers, timeout=timeout / 1000 + 1)
            if response.status_code == 200:
                return response.json()['delay']
            return 0
        except requests.exceptions.RequestException:
            return 0
        except Exception as e:
            logger.debug(f"测试节点 {name} 延迟时发生错误: {str(e)}")
            return 0

    def switch_proxy(self, selector_name, proxy_name):
        """切换代理"""
        url = f'{self.base_url}/proxies/{selector_name}'
        data = {"name": proxy_name}
        response = requests.put(url, json=data, headers=self.headers)
        return response.status_code == 204
class ProxyType(Enum):
    TRANSIT = "中转"
    IEPL = "IEPL"
    ADVANCED_IEPL = "进阶IEPL"
class ProxyManager:
    def __init__(self, config_path: Optional[str] = None):
        """
        初始化代理管理器

        Args:
            config_path: 配置文件路径，默认为当前目录下的 config.ini
        """
        if config_path is None:
            script_dir = os.path.dirname(os.path.abspath(__file__))
            config_path = os.path.join(script_dir, "config.ini")

        if not os.path.exists(config_path):
            raise FileNotFoundError(f"配置文件不存在: {config_path}")

            # 读取配置
        cf = configparser.ConfigParser()
        cf.read(config_path, encoding='utf-8')

        self.port = cf.get('clash api', 'port', fallback='9090')
        self.secret = cf.get('clash api', 'secret', fallback='')
        self.selector = cf.get('clash api', 'selector', fallback='GLOBAL')
        self.preferred_type = cf.get('clash api', 'preferred_type', fallback='IEPL,进阶IEPL').split(',')

        # 初始化API和其他设置
        self.clash = ClashAPI(self.port, self.secret)
        self.current_proxy_info = None
        self.test_timeout = 3000
        self.max_test_nodes = 10

        # 需要过滤的节点关键词
        self.filter_keywords = ['剩余流量', '距离下次重置', '套餐到期']

    def get_proxy_type(self, proxy_name: str) -> Optional[ProxyType]:
        """
        获取代理节点类型
        """
        if '进阶IEPL' in proxy_name:
            return ProxyType.ADVANCED_IEPL
        elif 'IEPL' in proxy_name:
            return ProxyType.IEPL
        elif '中转' in proxy_name:
            return ProxyType.TRANSIT
        return None

    def is_preferred_type(self, proxy_name: str) -> bool:
        """
        检查是否是优先使用的节点类型
        """
        return any(ptype in proxy_name for ptype in self.preferred_type)

    async def switch_to_better_proxy(self, cookie_switcher=None) -> Optional[Dict]:
        """
        切换到更好的代理节点并刷新 cookie

        Args:
            cookie_switcher: cookie切换函数，接收force_refresh参数
        """
        try:
            proxies_info = self.clash.get_all_proxies()
            if not proxies_info:
                logger.error("获取代理信息失败")
                return None

            selector_info = self.clash.get_proxy_info(self.selector)
            if not selector_info:
                logger.error(f"获取选择器 {self.selector} 信息失败")
                return None

            current_proxy = selector_info.get('now')
            current_type = self.get_proxy_type(current_proxy)
            logger.info(f'当前使用的代理: {current_proxy} (类型: {current_type.value if current_type else "未知"})')

            # 过滤代理节点
            available_proxies = selector_info.get('all', [])
            filtered_proxies = [
                proxy for proxy in available_proxies
                if not any(x in proxy for x in self.filter_keywords)
                   and proxy != current_proxy
                   and '香港' in proxy
            ]

            # 优先选择首选类型的节点
            preferred_proxies = [p for p in filtered_proxies if self.is_preferred_type(p)]
            test_proxies = preferred_proxies if preferred_proxies else filtered_proxies

            if not test_proxies:
                logger.warning("没有可用的节点")
                return None

                # 随机选择部分节点进行测试
            if len(test_proxies) > self.max_test_nodes:
                test_proxies = random.sample(test_proxies, self.max_test_nodes)

            available_nodes = []
            logger.info(f"正在测试节点延迟... (优先测试{','.join(self.preferred_type)}类型)")

            for proxy in test_proxies:
                proxy_type = self.get_proxy_type(proxy)
                delay = self.clash.get_proxy_delay(proxy, timeout=self.test_timeout)
                if delay > 0:
                    available_nodes.append({
                        'name': proxy,
                        'type': proxy_type.value if proxy_type else "未知",
                        'delay': delay
                    })
                    logger.info(f"节点: {proxy} (类型: {proxy_type.value if proxy_type else '未知'}), 延迟: {delay}ms")
                time.sleep(0.5)

                if len(available_nodes) >= 3:
                    break

            if not available_nodes:
                logger.warning("没有响应的节点，随机选择一个节点")
                random_proxy = random.choice(test_proxies)
                random_type = self.get_proxy_type(random_proxy)
                selected_node = {
                    'name': random_proxy,
                    'type': random_type.value if random_type else "未知",
                    'delay': 0
                }
            else:
                # 按延迟排序
                available_nodes.sort(key=lambda x: x['delay'])
                selected_node = available_nodes[0]

                # 切换代理
            if self.clash.switch_proxy(self.selector, selected_node['name']):
                logger.info(
                    f'成功切换到代理: {selected_node["name"]}\n'
                    f'类型: {selected_node["type"]}\n'
                    f'延迟: {selected_node["delay"]}ms'
                )

                # 切换成功后刷新 cookie
                if cookie_switcher:
                    try:
                        new_cookies = await cookie_switcher(force_refresh=True)
                        logger.info("Cookie刷新成功")
                        selected_node['cookies'] = new_cookies
                    except Exception as e:
                        logger.error(f"刷新Cookie失败: {str(e)}")

                self.current_proxy_info = selected_node
                return selected_node
            else:
                logger.error('切换代理失败')
                return None

        except Exception as e:
            logger.error(f"切换代理时发生错误: {str(e)}")
            return None

    def get_current_proxy_info(self) -> Optional[Dict]:
        """获取当前代理信息"""
        return self.current_proxy_info

# ====================================== 初始化函数 ======================================
class TelegramBot:
    @staticmethod
    def send_message_tg(text, chat_id=-1002310216390):
        url = f'https://api.telegram.org/bot7657341835:AAEMX1kNYSjZExa7HAnuBLS76NJJKb2Ff0A/sendMessage'
        payload = {'chat_id': chat_id, 'text': text, 'parse_mode': 'Markdown'}
        response = requests.post(url, json=payload)
        return response.json()


def cleanup():
    """清理程序资源"""
    global _js_content, _js_context,vname
    _js_content = None
    _js_context = None
    vname = None
    logger.info("已清理JS相关资源")

# ====================================== cookie操作相关 ======================================
def cookie_dict_to_str(cookies):
    # Create a list of key=value pairs
    cookie_list = [f"{key}={value}" for key, value in cookies.items()]
    # Join the list into a string with '; ' as the separator
    formatted_cookie_string = '; '.join(cookie_list)
    return formatted_cookie_string
def cookie_str_to_dict(cookie_str):
    cookie_str = cookie_str.strip()
    cookie_dict = {}
    cookie_items = cookie_str.split(';')
    for item in cookie_items:
        if not item.strip():
            continue
        if '=' in item:
            key, value = item.split('=', 1)
            cookie_dict[key.strip()] = value.strip()
    return cookie_dict
def load_cookies():
    try:
        with open(COOKIES_FILE, 'r') as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return None
def save_cookies(cookies):
    with open(COOKIES_FILE, 'w') as f:
        json.dump(cookies, f)
def get_cookies(force_new=False):
    """
    获取cookies
    :param force_new: 是否强制获取新cookies而不使用本地缓存
    """
    if not force_new:
        saved_cookies = load_cookies()
        if saved_cookies and 'pt_pin' in saved_cookies:
            logger.info(f"Using saved cookies with pt_pin: {saved_cookies['pt_pin']}")
            return saved_cookies

    max_retries = Config.MAX_RETRIES
    retry_delay = Config.RETRY_DELAY

    for attempt in range(max_retries):
        try:
            res = requests.get("http://20.212.179.103:8080/api/jd/getCookie")
            data = res.json()['data']
            cookies = cookie_str_to_dict(data)

            if 'pt_pin' in cookies:
                logger.info(f"Successfully got new cookies with pt_pin: {cookies['pt_pin']}")
                save_cookies(cookies)  # 保存新获取的cookies
                return cookies
            else:
                logger.warning(f"Attempt {attempt + 1}: pt_pin not found in cookies, retrying...")
                if attempt < max_retries - 1:
                    time.sleep(retry_delay)
                continue

        except Exception as e:
            logger.error(f"Error occurred in attempt {attempt + 1}: {str(e)}")
            if attempt < max_retries - 1:
                time.sleep(retry_delay)
                continue
            raise

    raise Exception("Failed to get valid cookies with pt_pin after maximum retries")

async def refresh_cookies():
    """强制获取新的cookies"""
    cookies = get_cookies(force_new=True)  # 强制获取新cookies
    if not cookies:
        raise Exception("Failed to get new cookies")

    global _current_cookies
    _current_cookies = cookies  # 更新全局cookies
    logger.info(f"已更新新的 cookies: {cookies.get('pt_pin', 'unknown')}")
    return cookies
async def initialize():
    """初始化程序必要组件"""
    global _current_cookies
    try:
        logger.info("JS上下文初始化成功")

        # 初始化 cookies
        _current_cookies = await refresh_cookies()
        logger.info("Cookies初始化成功")

        return True
    except Exception as e:
        logger.error(f"初始化失败: {e}")
        return False


async def get_and_refresh_cookies(force_refresh=False):
    global _current_cookies
    try:
        if force_refresh:
            logger.info("强制刷新cookies")
            return await refresh_cookies()

        if _current_cookies:
            return _current_cookies

        _current_cookies = await refresh_cookies()
        logger.info(f"成功获取新的Cookies: {_current_cookies.get('pt_pin', 'unknown')}")
        return _current_cookies

    except Exception as e:
        logger.error(f"获取cookies失败: {e}")
        raise